using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Threading;

namespace OpenWaves.Caching
{
    public class MonitorBasedLockManager : ILockManager
    {
        private readonly ConcurrentDictionary<string, MonitorLock> locks = new ConcurrentDictionary<string, MonitorLock>();

        public bool TryAcquireLock(string key, TimeSpan timeout)
        {

            var monitorLock = this.locks.GetOrAdd(key, new MonitorLock());

            lock (monitorLock)
            {
                while (monitorLock.Taken)
                {
                    //Debug.WriteLine("{0}: Before wait on {1}", Thread.CurrentThread.ManagedThreadId, monitorLock.Id);

                    monitorLock.WaitCount++;

                    var waitTimout = timeout == TimeSpan.MaxValue ? TimeSpan.FromMilliseconds(-1) : timeout;
                    try
                    {
                        if (Monitor.Wait(monitorLock, waitTimout) == false)
                        {
                            //Debug.WriteLine("{0}: Wait returned false", Thread.CurrentThread.ManagedThreadId);
                            return false;
                        }
                        //Debug.WriteLine("{0}: Wait returned true", Thread.CurrentThread.ManagedThreadId);
                    }
                    finally
                    {
                        monitorLock.WaitCount--;
                    }
                }

                monitorLock.Taken = true;
                this.locks.TryAdd(key, monitorLock);
                return true;
            }

        }

        public void ReleaseLock(string key)
        {
            MonitorLock monitorLock;
            var monitorLockFound = this.locks.TryGetValue(key, out monitorLock);
            Contract.Assert(monitorLockFound, "Cannot release a lock that was not previously acquired.");

            lock (monitorLock)
            {
                monitorLock.Taken = false;
                if (monitorLock.WaitCount == 0)
                {
                    this.locks.TryRemove(key, out monitorLock);
                }
                //Debug.WriteLine("{0}: Pulsing {1}", Thread.CurrentThread.ManagedThreadId, monitorLock.Id);
                Monitor.Pulse(monitorLock);
            }
        }

        private class MonitorLock
        {
            public readonly string Id = Guid.NewGuid().ToString();
            public bool Taken;
            public int WaitCount;
        }
    }
}